package util;

import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class SafeRingBuffer<T> implements BlockingQueue<T> {
	
	private int newest;
	private int oldest;
	private final Object[] data;
	private int modCount;
	
	public SafeRingBuffer(int capacity){
		data = new Object[capacity + 1];
		newest = 0;
		oldest = 1;
		modCount = 0;
	}
	
	private synchronized boolean incNewest(){
		if(newest == (oldest + data.length - 2) % data.length)
			return false;
		newest = (newest + 1) % data.length;
		modCount++;
		return true;
	}
	
	private synchronized void incOldest(){
		oldest = (oldest + 1) % data.length;
		modCount++;
	}
	
	private synchronized boolean decNewest() {
		if (isEmpty())
			return false;
		newest = (newest - 1 + data.length) % data.length;
		modCount++;
		return true;
	}

	@Override
	public synchronized T element() {
		if(isEmpty())
			throw new NoSuchElementException("Queue is empty");
		return (T) data[oldest];
	}

	@Override
	public synchronized T peek() {
		return (T) data[oldest];
	}

	@Override
	public synchronized T remove() {
		if (isEmpty())
			throw new NoSuchElementException("Queue is empty");
		else {
			T rtn = (T)data[oldest];
			incOldest();
			return rtn;
		}
	}

	@Override
	public boolean isEmpty() {
		return size() == 0;
	}
	
	public boolean isFull() {
		return remainingCapacity() == 0;
	}

	@Override
	public Iterator<T> iterator() {
		return new SRBIterator<T>();
	}

	@Override
	public synchronized int size() {
		return (newest + 1 - oldest + data.length) % data.length;
	}

	@Override
	public synchronized boolean add(T o) {
		if(o == null)
			throw new NullPointerException("This queue does not accept null elements");
		if(!incNewest())
			throw new IllegalStateException("Queue full.");
		data[newest] = o;
		return true;
	}

	@Override
	public synchronized boolean contains(Object o) {
		T object = (T)o;
		for (int index = oldest; index != (newest+1); index = (index+1) % data.length) {
			if (object.equals(data[index]))
				return true;
		}
		return false;
	}

	@Override
	public synchronized int drainTo(Collection<? super T> c) {
		if(c == this)
			throw new IllegalArgumentException("This queue cannot drain to itself");
		int transferred = 0;
		while(!isEmpty()){
			c.add(poll());
			transferred++;
		}
		return transferred;
	}

	@Override
	public synchronized int drainTo(Collection<? super T> c, int maxElements) {
		if(c == this)
			throw new IllegalArgumentException("This queue cannot drain to itself");
		int transferred = 0;
		while(!isEmpty() && transferred < maxElements){
			c.add(poll());
			transferred++;
		}
		return transferred;
	}

	@Override
	public synchronized boolean offer(T o) {
		if(o == null)
			throw new NullPointerException("This queue does not accept null elements");
		if(isFull())
			return false;
		incNewest();
		data[newest] = o;
		return true;
	}

	@Override
	public synchronized boolean offer(T o, long timeOut, TimeUnit tUnit) throws InterruptedException {
		if(o == null)
			throw new NullPointerException("This queue does not accept null elements");
		long startTime = System.currentTimeMillis();
		long limit = tUnit.toMillis(timeOut);
		while(isFull()){
			wait(Math.max(limit - System.currentTimeMillis() + startTime, 0));
			if(System.nanoTime() - startTime >= limit)
				return false;
		}
		return add(o);
	}

	@Override
	public synchronized T poll() {
		T rtn = (T) data[oldest];
		data[oldest] = null;
		if(rtn != null)
			incOldest();
		return rtn;
	}

	@Override
	public synchronized T poll(long timeOut, TimeUnit tUnit) throws InterruptedException {
		long startTime = System.currentTimeMillis();
		long limit = tUnit.toMillis(timeOut);
		while(isEmpty()){
			wait(Math.max(limit - System.currentTimeMillis() + startTime, 0));
			if(System.nanoTime() - startTime >= limit)
				return null;
		}
		return poll();
	}

	@Override
	public synchronized void put(T o) throws InterruptedException {
		while (isFull())
			wait();
		add(o);
	}

	@Override
	public int remainingCapacity() {
		return data.length - size() - 1;
	}

	@Override
	public synchronized boolean remove(Object o) {
		T object = (T)o;
		for (int index = oldest; index != (newest+1); index = (index+1) % data.length) {
			if (object.equals(data[index])) {
				if (index < oldest) { // index is < oldest so shift newest side of list down
					for (int i = index; i <= newest; i++)
						data[i] = data[i+1];
					decNewest();
				} else { // index is >= oldest so shift oldest side of list up
					for (int i = index; i >= oldest; i--)
						data[i] = data[(i-1+data.length) % data.length];
					incOldest();
				}
				return true;
			}
		}
		return false;
	}

	@Override
	public synchronized T take() throws InterruptedException {
		while(isEmpty())
			wait();
		T rtn = (T) data[oldest];
		data[oldest] = null;
		return rtn;
	}
	
	private class SRBIterator<T> implements Iterator<T>{

		private int current = oldest;
		private final int startModCount = modCount;
		
		@Override
		public boolean hasNext() {
			if(startModCount != modCount)
				throw new ConcurrentModificationException();
			return data[current] != null;
		}

		@Override
		public T next() {
			if(startModCount != modCount)
				throw new ConcurrentModificationException();
			T rtn = (T) data[current];
			if(rtn == null)
				throw new NoSuchElementException();
			current = (current + 1 + data.length) % data.length;
			return rtn;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
		
	}
	
	@Override
	public synchronized boolean equals(Object o) {
		if (!(o instanceof BlockingQueue)) return false;
		BlockingQueue srb = (BlockingQueue)o;
		if (size() != srb.size()) return false;
		while (!isEmpty()) {
			T next = poll();
			Object srbNext = srb.poll();
			if (!(next.equals(srbNext))) return false;
		}
		return true;
	}

	@Override
	public Object[] toArray() {
		throw new UnsupportedOperationException();
	}

	@Override
	public <T> T[] toArray(T[] arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean addAll(Collection<? extends T> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void clear() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean containsAll(Collection<?> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean removeAll(Collection<?> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean retainAll(Collection<?> arg0) {
		throw new UnsupportedOperationException();
	}

}
